-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

-- Description: fructrl 操作上下电相关功能

local log = require 'mc.logging'
local class = require 'mc.class'
local enums = require 'types.enums'
local c_service = require 'fructrl.service'
local skynet = require 'skynet'
local queue = require 'skynet.queue'
local utils = require 'types.utils'
local m_error_base = require 'messages.base'
local m_error_custom = require 'messages.custom'
local c_button_evt = require 'button_evt'
local multihost = require 'multi_host'
local nmi = require 'fructrl.nmi'
local force_reset = require 'fructrl.force_reset'
local power_button = require 'fructrl.pwr_button'
local therm_trip = require 'fructrl.therm_trip'
local context = require 'mc.context'
local record_fault = require 'fructrl.record_fault'
local pg_signal = require 'fructrl.pg_signal'
local sys_reset = require 'fructrl.sys_reset'
local power_lock = require 'fructrl.pwr_lock'
local synctool = require 'synctool'

local c_fructrl = class()

function c_fructrl:ctor(db, id)
    self.db = db
    self.obj = nil
    self.system_id = id
    self.sys_reset_flag = false -- BMC主动发命令导致系统复位时改为true，系统复位后改回false
    self.restart_channel = 0 -- 复位通道
    self.poweron_from_ipmi_flag = false -- 是否上电命令由ipmi发送，false否，true是
    self.board_power_drop = false -- 此标记用于记录是否发生二级AC闪断
    self.power_off_queue = queue()
    self.sys_reset_queue = queue()
    self.multihost_ins = multihost.get_instance()
    self.is_normal_off_task_exist = false
    self.is_force_off_task_exist = false
    self.last_act = '' -- 强制下电记P_OFF，上电记P_ON，用于记录哪个命令后来的
    self.chassis_poweron_strategy = nil
    self.fru_pg_signals = {}
end

function c_fructrl:set_restart_channel(val)
    self.restart_channel = val
end

function c_fructrl:get_restart_channel()
    return self.restart_channel
end

function c_fructrl:get_sys_reset_flag()
    return self.sys_reset_flag
end

function c_fructrl:set_sys_reset_flag(val)
    log:notice_easy("[System:%s]Set sys_reset_flag to %s", self.system_id, val)
    self.sys_reset_flag = val
end

function c_fructrl:set_poweron_from_ipmi_flag(val)
    self.poweron_from_ipmi_flag = val
    log:notice_easy('[System:%s]set poweron from ipmi flag to %s', self.system_id, val)
end

function c_fructrl:wait_pwronlocked_unlock()
    log:notice('[System:%s]wait PwrOnLocked unlock...', self.system_id)
    while true do
        -- 等待上电锁解锁时，允许上电动作被强制下电操作打断
        if self.last_act == 'P_OFF' then
            return false
        end
        if not self:get_PwrOnLocked() then
            log:notice('[System:%s]Quit wait_pwronlocked_unlock already.', self.system_id)
            break
        end
        skynet.sleep(200)
    end
    return true
end

local function before_poweron(self)
    self.last_act = 'P_ON'
    self.therm_trip:clear_therm_trip() -- 清除高温下电标记
    self.record_fault:clear_record_fault() -- 清除电源告警锁存
    local stat = self.obj.PowerState
    if stat == enums.PowerStatus.ON or stat == enums.PowerStatus.ONING then
        log:error_easy('[System:%s]Power state is already ON or ONING, no need poweron!', self.system_id)
        return false
    end

    local time_out = 3 -- 供其他App响应信号的时间

    local count = 0

    if self:wait_pwronlocked_unlock() then
        -- 广播上电前信号
        log:notice('[System:%s]Send pre-poweron broadcast after all locks are unlocked.', self.system_id)
        c_service:FruCtrlSystemsFruCtrlBeforePowerOnSignal(self.obj, time_out)
    end

    while true do
        skynet.sleep(100)
        count = count + 1

        -- 上电之后又发了强制下电，就终止上电
        if self.last_act == 'P_OFF' then
            log:notice_easy('[System:%s]Interrupt power on because force power off.', self.system_id)
            return false
        end

        if count >= time_out then
            count = time_out
            -- 等其他App处理完信号后，开始等待解锁
            if not self:get_PwrOnLocked() then
                log:notice_easy('[System:%s]Power on after all locks are unlocked.', self.system_id)
                return true
            end
        end
    end
end

local function poweron(self)
    -- 获取上一次电源事件
    local last_power_event = self.obj.LastPowerEvent
    if self.poweron_from_ipmi_flag then
        -- ipmi标准命令get chassis status中的Last Power Event，第5个bit为判断上电命令是否由ipmi发送
        last_power_event = last_power_event | 0x10
        self:set_last_power_event(last_power_event)
        log:notice_easy('[System:%s]The poweron cmd is sent by the ipmi', self.system_id)
        -- 清除上电命令来自ipmi的标记
        self:set_poweron_from_ipmi_flag(false)
    else
        last_power_event = last_power_event & 0xEF
        self:set_last_power_event(last_power_event)
    end
    if self:get_PowerState() == tostring(enums.PowerStatus.ON) then
        log:notice_easy('[System:%s]PowerState has changed to ON, no need to execute power on', self.system_id)
        return true
    end
    log:notice_easy('[System:%s]execute power on', self.system_id)
    -- 先记录上电过程中
    self:set_PowerState(tostring(enums.PowerStatus.ONING))
    if self.obj.PowerState == enums.PowerStatus.OFFING then
        -- 防止长按信号和短按信号同时到cpld
        skynet.sleep(200)
    end
    -- 开始上电
    self.sys_reset_queue(function()
        self.power_button:push_button_short()
        self:set_sys_reset_flag(true)
    end)

    -- 等待上电完成或超时(超时时间默认值为10s)
    local tim = self.obj.PowerOnTimeoutSeconds
    log:notice_easy('[System:%s]Powering on, the poweron timeout is (%u) seconds', self.system_id, tim)
    while true do
        skynet.sleep(100)

        if self.obj.PowerState == enums.PowerStatus.ON then
            -- 上电成功，刷新电源故障
            self.pg_signal:set_poweron_fault_flag(false)
            self:update_ctrl_fault()
            return true
        end

        tim = tim - 1
        if tim <= 0 then
            -- 上电超时表示硬件电路故障，仅记录日志，需要电路修复后通过清除cpld的超时标记或AC才能恢复。
            -- 沿用V2处理方式，防误触功能开启后也可以上电
            self.power_button:push_button_long()
            -- 上电失败，刷新电源故障
            self.pg_signal:set_poweron_fault_flag(true)
            self:update_ctrl_fault()
            skynet.sleep(500)
            if self.obj.PowerState == enums.PowerStatus.ON then
                log:notice_easy("[System:%s]power on by pressing long button, power on fail by pressing short button",
                    self.system_id)
                return true
            end
            log:error_easy("[System:%s]Power on is over time! Power on fail!", self.system_id)
            log:maintenance(log.MLOG_ERROR, log.FC__PUBLIC_OK, 'Fru0 power on failed.')
            return false
        end
    end
end

-- 下电超时检查函数，超时返回true
local function power_off_timeout_check(self, tim, fruid)
    local stat
    for _ = 1, tim do
        skynet.sleep(100) -- 1s延时
        stat = self:get_FruPowerState(fruid)
        if stat == enums.PowerStatus.OFF then
            log:notice_easy("[System:%s]Power off within the specified time.", self.system_id)
            self:set_PowerState(enums.PowerStatus.OFF)
            return false
        elseif stat == enums.PowerStatus.ONING then
            log:notice_easy("[System:%s]The power-on command is received during the power-off process", self.system_id)
            self:set_PowerState(enums.PowerStatus.ON)
            return false
        elseif self.is_force_off_task_exist then
            log:notice('abort power off due to forced power off')
            return false
        end
    end
    return true
end

local function poweroff(self, fruid)
    if self.is_normal_off_task_exist then
        log:notice_easy('[System:%s]power off task is exist, do not creat it again', self.system_id)
        return 0
    end
    self.is_normal_off_task_exist = true
    return self.power_off_queue(function()
        local need_forceoff_again = false
        log:notice_easy('[System:%s]execute power off', self.system_id)
        -- 下电状态和下电中都不再执行，防止短按被当成上电
        local stat = self:get_FruPowerState(fruid)
        if stat == enums.PowerStatus.OFF or stat == enums.PowerStatus.OFFING then
            log:error_easy('[System:%s]Power state is already OFF or OFFING, no need poweroff!', self.system_id)
            self.is_normal_off_task_exist = false
            return 1
        end

        -- 记录下电过程中，并开始下电
        if self.obj.PowerState ~= enums.PowerStatus.OFF and self.obj.PowerState ~= enums.PowerStatus.OFFING then
            self:set_PowerState(tostring(enums.PowerStatus.OFFING))
        end
        self.power_button:push_button_short(fruid)

        if self.obj.GracefulShutdownTimeoutEnabled ~= 0 then
            local tim = self.obj.GracefulShutdownTimeoutSeconds
            if power_off_timeout_check(self, tim, fruid) and self:get_FruPowerState(fruid) ~= enums.PowerStatus.OFF then
                -- 超时后转强制下电
                log:error_easy("[System:%s]GracefulShutdown over time (%d)s, execute force power off!!!",
                    self.system_id, tim)
                self.power_button:push_button_long(fruid)
                need_forceoff_again = true
            else
                -- 下电成功打断/强制下电任务打断/上电命令打断
                self.pg_signal:set_poweroff_fault_flag(false)
                self:update_ctrl_fault()
                self.is_normal_off_task_exist = false
                return 0
            end
        end

        if need_forceoff_again and power_off_timeout_check(self, 15, fruid) then
            self.power_button:push_button_long(fruid)
            log:notice_easy("[System:%s]PowerState is on due to long button, need forced power off again!!!",
                self.system_id)
        end

        -- 下电超时时间为15s
        if power_off_timeout_check(self, 15, fruid) then
            -- 确定是否还处于上电状态，并刷新电源故障事件
            self.pg_signal:check_poweroff_fault()
            self:update_ctrl_fault()
            if self.obj.PowerState == enums.PowerStatus.OFFING and self.pg_signal:get_PGSignal() ~= 0 then
                log:notice_easy('[System:%s]Force Power off timeout, change status to on', self.system_id)
                self:set_PowerState(enums.PowerStatus.ON)
            end
        end
        self.is_normal_off_task_exist = false
        return 0
    end)
end

local function poweroff_force(self)
    self.last_act = 'P_OFF'
    if self.is_force_off_task_exist then
        log:notice_easy('[System:%s]force power off task is exist, do not creat it again', self.system_id)
        return 0
    end
    self.is_force_off_task_exist = true
    return self.power_off_queue(function()
        log:notice_easy('[System:%s]execute force power off', self.system_id)
        local stat = self.obj.PowerState
        if stat == tostring(enums.PowerStatus.OFF) then
            log:error_easy('[System:%s]Power state is OFF, no need force poweroff!', self.system_id)
            skynet.sleep(300) -- 延时3s确保上电前等待阶段能识别到发生过强制下电
            self.is_force_off_task_exist = false
            return 1
        end

        -- 先记录下电过程中
        self:set_PowerState(tostring(enums.PowerStatus.OFFING))
        if stat == enums.PowerStatus.ONING or stat == enums.PowerStatus.OFFING then
            -- 防止长按信号和短按信号同时到cpld
            skynet.sleep(200)
        end
        -- 开始强制下电
        self.power_button:push_button_long()

        -- 间隔几秒防止外部注入多次强制下电指令,10s仍未下电，强制下电超时
        skynet.sleep(1000)
        -- 确定是否还处于上电状态，并刷新电源故障事件
        self.pg_signal:check_poweroff_fault()
        self:update_ctrl_fault()
        self.is_force_off_task_exist = false
        if self.obj.PowerState == enums.PowerStatus.OFFING and self.pg_signal:get_PGSignal() ~= 0 then
            log:notice_easy('[System:%s]Force power off timeout, change status to on', self.system_id)
            self:set_PowerState(enums.PowerStatus.ON)
        end
        return 0
    end)
end

local function create_nmi_obj(self, object)
    self.nmi = nmi.new(object, self.system_id)
end

local function create_force_reset_obj(self, object, position)
    if not self.force_reset then
        self.force_reset = force_reset.new(self)
    end
    if not object.IsValid then
        log:error_easy('ignore invalid ForceReset obj, position is %s', position)
        return
    end
    self.force_reset:insert_obj(object, position)
end

local function create_power_button_obj(self, object)
    if not self.power_button then
        self.power_button = power_button.new(self.system_id)
        -- PowerButton对象分发完毕，对外提供上下电功能
        self.before_poweron = before_poweron
        self.poweron = poweron
        self.poweroff_force = poweroff_force
        self.poweroff = poweroff
        log:notice_easy('[System:%s]the power-on and power-off functions are provided', self.system_id)
    end
    self.power_button:insert_obj(object)
end

local function create_therm_trip_obj(self, object)
    self.therm_trip = therm_trip.new(object, self.system_id)
    if self.obj and not self.obj.ActionModified then
        self.obj.Action = self.therm_trip:get_thermtrip_actions()
    end

    local therm_trop_cb = function()
        local actions = self.obj.Action
        local time = self.therm_trip:get_thermtrip_seconds()
        if actions == 1 then
            log:notice_easy('[System:%s]Thermtrip policy is PowerON, do power on after (%u)s.', self.system_id, time)
        elseif actions == 0 then
            log:notice_easy('[System:%s]Thermtrip policy is NoAction.', self.system_id)
        else
            log:notice_easy('[System:%s]detected wrong actions (%s)', self.system_id, actions)
        end
        
        skynet.sleep(time * 100) -- 延时time*100*10ms

        if actions == 1 then
            -- 上电
            local ctx_new = context.get_context_or_default()
            local ok, rsp = pcall(self.obj.PowerCtrl, self.obj, ctx_new, enums.PowerCtrlType.On,
                enums.RestartCause.ChassisControlCommand)
            if not ok then
                log:error_easy('[System:%s]Thermtrip call PowerCtrl rpc to power on has error (%s)',
                    self.system_id, rsp)
            end
        end
    end

    self.therm_trip:register_therm_trip_callback(therm_trop_cb)
end

local function create_record_fault_obj(self, object)
    if not self.record_fault then
        self.record_fault = record_fault.new(self.system_id)
    end
    self.record_fault:insert_object(object)
end

local function create_pg_signal_obj(self, object)
    log:notice_easy('[System:%s] Add Fru %s PGSignal.', self.system_id, object.FruID)
    if object.FruID ~= 0 then
        self.fru_pg_signals[object.FruID] = pg_signal.new(self, object)
        return
    end
    self.pg_signal = pg_signal.new(self, object)
    skynet.fork_once(function()
        -- 保证初始化powerstate时,必有fructrl对象
        -- 限制重试次数,最多等待15秒
        local times = 30
        while not self.obj do
            skynet.sleep(50)
            times = times - 1
            if times <= 0 then
                log:error('[System:%s] pg_signal waits for the distribution of \
                    FruCtrl for more than 15 seconds.', self.system_id)
                return
            end
        end
        self.pg_signal:set_powerstate_init_value()
    end)
end

local function create_sys_reset_obj(self, object, position)
    if not object.IsValid then
        log:error_easy('[System:%s]ignore invalid SysRst obj, position is %s', self.system_id, position)
        return
    end
    self.sys_reset = sys_reset.new(self, object)
end

function c_fructrl:create_obj_callback(class_name, object, position)
    local obj_handle = {
        ['Nmi'] = create_nmi_obj,
        ['ForceReset'] = create_force_reset_obj,
        ['PowerButton'] = create_power_button_obj,
        ['ThermTrip'] = create_therm_trip_obj,
        ['RecordFault'] = create_record_fault_obj,
        ['PGSignal'] = create_pg_signal_obj,
        ['SysReset'] = create_sys_reset_obj,
    }

    if obj_handle[class_name] then
        obj_handle[class_name](self, object, position)
    end
end

local function check_delay_poweron_mode(value)
    if not enums.PowerOnDelayMode[value] then
        error(m_error_base.PropertyValueNotInList('%PowerOnDelayMode:' .. value, '%PowerOnDelayMode'))
    end
end

local function check_decimal(num)
    local pattern = "^%d+%.?%d?$" -- 匹配数字和小数点，小数点后最多只有一位数字
    if type(num) == "number" then
        num = tostring(num)
    end
    return type(num) == "string" and string.match(num, pattern)
end

local function check_delay_poweron_seconds(value)
    if value < 0 or value > 120 then -- 门限为0到120s
        error(m_error_custom.ValueOutOfRange('%Seconds'))
    end
    if not check_decimal(value) then
        error(m_error_base.PropertyValueFormatError(tostring(value), '%Seconds'))
    end
end

local function check_poweron_strategy(value)
    if not enums.PowerRestorePolicy[value] then
        error(m_error_base.PropertyValueNotInList('%PowerOnStrategy:' .. value, '%PowerOnStrategy'))
    end
end

local function check_poweroff_timeout(value)
    if value < 10 or value > 6000 then -- 门限为10到6000s
        error(m_error_custom.ValueOutOfRange('%GracefulShutdownTimeoutSeconds'))
    end
end

local function set_delay_poweron_mode(self, val)
    log:notice_easy('[System:%s]Set power up delay mode to (%s) successfully', self.system_id, val)
    utils.record_prop_change_operation_log(self.multihost_ins:is_multihost_type() and self.system_id or nil,
        string.format('Set power up delay mode to (%s) successfully', val))
end

local function set_delay_poweron_seconds(self, val)
    log:notice_easy('[System:%s]Set power up delay time to (%.1f s) successfully', self.system_id, val)
    utils.record_prop_change_operation_log(self.multihost_ins:is_multihost_type() and self.system_id or nil,
        string.format('Set power up delay time to (%.1f s) successfully', val))
end

local function set_poweron_strategy(self, val)
    log:notice_easy('[System:%s]Set power restore policy to (%s) successfully', self.system_id, val)
    utils.record_prop_change_operation_log(self.multihost_ins:is_multihost_type() and self.system_id or nil,
        string.format('Set power restore policy to (%s) successfully', val))
end

local function set_poweron_strategy_exceptions(self, val)
    if val == 1 then
        utils.record_prop_change_operation_log(self.multihost_ins:is_multihost_type() and self.system_id or nil,
            'Set power on strategy exceptions (FirmwareActive) successfully')
    elseif val == 0 then
        utils.record_prop_change_operation_log(self.multihost_ins:is_multihost_type() and self.system_id or nil,
            'Set power on strategy exceptions () successfully')
    end
end

local function set_poweroff_timeout(self, val)
    self:set_PowerOffTimeoutEN(val ~= 0 and 1 or 0)
    log:notice_easy('[System:%s]Set graceful shutdown timeout to (%u) seconds successfully', self.system_id, val)
    utils.record_prop_change_operation_log(self.multihost_ins:is_multihost_type() and self.system_id or nil,
        string.format('Set graceful shutdown timeout to (%u) seconds successfully', val))
end

local function set_poweroff_enable(self, val)
    if val == 0 then
        utils.record_prop_change_operation_log(self.multihost_ins:is_multihost_type() and self.system_id or nil,
            string.format('Set power off timeout to (disable) successfully'))
    elseif val == 1 then
        utils.record_prop_change_operation_log(self.multihost_ins:is_multihost_type() and self.system_id or nil,
            string.format('Set power off timeout to (enable) successfully'))
    else
        utils.record_prop_change_operation_log(self.multihost_ins:is_multihost_type() and self.system_id or nil,
            string.format('Set power off timeout failed'))
        log:error_easy("[System:%s]pwr_off_timeout_en:%u is invalid.", self.system_id, val)
    end
end

local function set_panel_power_button_enabled(self, val, sender)
    local button_evt = c_button_evt:get_instance()
    -- 有软件上电锁且试图解除按钮屏蔽功能时，只打印日志，跳过写寄存器
    if not self.multihost_ins:is_multihost_type() and val and self:get_PwrOnLocked() then
        log:notice("The panel button is shielded again due to the soft power on lock")
        goto continue
    end
    button_evt:set_PwrButtonLock(val and 0 or 1) -- PanelPowerButtonEnabled为true代表按钮功能有效，需关闭屏蔽功能
    ::continue::
    log:notice_easy('[System:%s]Set power button lock to (%s) successfully', self.system_id, val and "off" or "on")
    utils.record_prop_change_operation_log(self.multihost_ins:is_multihost_type() and self.system_id or nil,
        string.format('Set power button lock to (%s) successfully', val and "off" or "on"))
    -- 防止出现无限循环的广播更新
    if sender then
        synctool:get_instance():update_value_by_name(self.system_id, 'PanelPowerButtonEnabled', val)
    end
end

function c_fructrl:props_changed_callback(name, value, sender)
    local prop_handle = {
        ['Mode'] = set_delay_poweron_mode,
        ['Seconds'] = set_delay_poweron_seconds,
        ['PowerOnStrategy'] = set_poweron_strategy,
        ['PowerOnStrategyExceptions'] = set_poweron_strategy_exceptions,
        ['GracefulShutdownTimeoutSeconds'] = set_poweroff_timeout,
        ['GracefulShutdownTimeoutEnabled'] = set_poweroff_enable,
        ['PanelPowerButtonEnabled'] = set_panel_power_button_enabled,
    }
    if prop_handle[name] then
        prop_handle[name](self, value, sender)
    end
end

function c_fructrl:props_before_change_callback(name, value)
    local prop_handle = {
        ['Mode'] = check_delay_poweron_mode,
        ['Seconds'] = check_delay_poweron_seconds,
        ['PowerOnStrategy'] = check_poweron_strategy,
        ['GracefulShutdownTimeoutSeconds'] = check_poweroff_timeout,
    }
    if prop_handle[name] then
        return prop_handle[name](value)
    end
end

function c_fructrl:thermaltrip_listen(name, value, sender)
    if name == 'Action' then
        if not self.obj.ActionModified and sender then
            self.obj.ActionModified = true
        end
        log:notice_easy("[System%s] [%s] changed to [%s], from [%s]", self.system_id, name, value, sender)
        if sender then
            utils.record_prop_change_operation_log(self.multihost_ins:is_multihost_type() and self.system_id or nil,
                string.format("%s the power on policy after cpu thermal trip successfully",
                    (value == 1) and 'Enabled' or 'Disabled'))
        end
    end
    if name == 'TimeDelaySeconds' then
        if not self.obj.TimeDelaySecondsModified and sender then
            self.obj.TimeDelaySecondsModified = true
        end
        log:notice_easy("[System%s] [%s] changed to [%s], from [%s]", self.system_id, name, value, sender)
        if sender then
            utils.record_prop_change_operation_log(self.multihost_ins:is_multihost_type() and self.system_id or nil,
                string.format(
                    "Set delay time to %u seconds for executing power on policy after cpu thermal trip successfully",
                    value))
        end
    end
end

function c_fructrl:init_thermaltrip_action()
    -- 处理兼容问题，如果未设置过持久化变量，优先csr生效
    if not self.obj.ActionModified and self.therm_trip and self.therm_trip:get_thermtrip_actions() then
        self.obj.Action = self.therm_trip:get_thermtrip_actions()
    end
end

function c_fructrl:set_fru_obj(object, power_on_lock_info)
    self.obj = object
    log:notice_easy("[System:%s]set fructrl object successfully, uptime: %s.", self.system_id, utils.uptime())
    self.obj.FruType = 'OS'
    self.power_on_lock = power_lock.new(self.obj, 'on')
    for app_name, reason in pairs(power_on_lock_info) do
        self.power_on_lock:power_lock_entrance(true, 0xFFFF, app_name, reason)
        log:notice("[System%s]add forever power on lock, app:%s reason:%s", self.system_id, app_name, reason)
    end
    -- 整机下电锁现在不使用，不进行适配
    self.power_off_lock = power_lock.new(self.obj, 'off')

    object:get_mdb_object('bmc.kepler.Systems.DelayedPowerOnPolicy').property_before_change:on(function(name, value)
        self:props_before_change_callback(name, value)
    end)
    object:get_mdb_object('bmc.kepler.Systems.DelayedPowerOnPolicy').property_changed:on(function(name, value)
        self:props_changed_callback(name, value)
    end)
    object:get_mdb_object('bmc.kepler.Systems.FruCtrl').property_before_change:on(function(name, value)
        self:props_before_change_callback(name, value)
    end)
    object:get_mdb_object('bmc.kepler.Systems.FruCtrl').property_changed:on(function(name, value, sender)
        self:props_changed_callback(name, value, sender)
    end)

    object:get_mdb_object('bmc.kepler.Systems.ThermalTrip').property_changed:on(function(name, value, sender)
        self:thermaltrip_listen(name, value, sender)
    end)

    -- 组件复位后恢复上电锁任务，单次任务
    skynet.fork_once(function()
        if self:get_PwrOnLocked() then
            log:notice_easy('[System:%s]fructrl reset, need to restart power on lock task', self.system_id)
            self.power_on_lock:restart_task()
        end
        if self:get_PowerOffLocked() then
            log:notice_easy('[System:%s]fructrl reset, need to restart power off lock task', self.system_id)
            self.power_off_lock:restart_task()
        end
    end)

    self:set_PreviousPowerState(self:get_PwrStateBeforeACLost() and "ON" or "OFF")
    self:init_thermaltrip_action()
    self:set_defaulte_poweron_strategy()
end

local function get_operation(obj, prop)
    local ret = nil
    local ok, msg = pcall(function ()
        ret = obj[prop]
    end)
    if not ok then
        log:error_easy('get prop %s failed, error_msg:%s', prop, msg)
    end
    return ret
end

local function set_operation(obj, prop, value)
    local ok, msg = pcall(function ()
        obj[prop] = value
    end)
    if not ok then
        log:error_easy('set prop %s to %s failed, error_msg:%s', prop, value, msg)
        return enums.RetValue.ERR
    end
    return enums.RetValue.OK
end

function c_fructrl:set_CurrentRestartType(id)
    return set_operation(self.obj, 'CurrentRestartType', id)
end
 
function c_fructrl:get_CurrentRestartType()
    return get_operation(self.obj, 'CurrentRestartType')
end

function c_fructrl:get_PowerState()
    return get_operation(self.obj, 'PowerState')
end

function c_fructrl:set_PowerState(str)
    return set_operation(self.obj, 'PowerState', str)
end

function c_fructrl:set_CurrentRestartCause(str)
    return set_operation(self.obj, 'CurrentRestartCause', str)
end

function c_fructrl:get_CurrentRestartCause()
    return get_operation(self.obj, 'CurrentRestartCause')
end

function c_fructrl:set_CurrentRestartCauseId(id)
    return set_operation(self.obj, 'CurrentRestartCauseId', id)
end

function c_fructrl:add_RestartCauseRecords(str)
    local ret = self:set_CurrentRestartCause(str)
    if ret == enums.RetValue.ERR then
        return ret
    end

    -- 先获取上次值
    local val = get_operation(self.obj, 'RestartCauseRecords')
    if not val then
        return enums.RetValue.ERR
    end

    -- 头部插入新值
    table.insert(val, 1, str)
    -- 限制容量
    local cap = 10
    if val[cap + 1] ~= nil then table.remove(val, cap + 1) end

    -- 写回
    return set_operation(self.obj, 'RestartCauseRecords', val)
end

function c_fructrl:set_PowerOnStrategy(str)
    return set_operation(self.obj, 'PowerOnStrategy', str)
end

function c_fructrl:get_PowerOnStrategy()
    return get_operation(self.obj, 'PowerOnStrategy')
end

function c_fructrl:set_PowerOffTimeoutEN(val)
    return set_operation(self.obj, 'GracefulShutdownTimeoutEnabled', val)
end

function c_fructrl:get_PowerOffTimeoutEN()
    return get_operation(self.obj, 'GracefulShutdownTimeoutEnabled')
end

function c_fructrl:set_PowerOffTimeout(time)
    return set_operation(self.obj, 'GracefulShutdownTimeoutSeconds', time)
end

function c_fructrl:get_PowerOffTimeout()
    return get_operation(self.obj, 'GracefulShutdownTimeoutSeconds')
end

function c_fructrl:set_HotswapState(str)
    return set_operation(self.obj, 'HotswapState', str)
end

function c_fructrl:get_PanelPowerButtonEnabled()
    return get_operation(self.obj, 'PanelPowerButtonEnabled')
end

-- 模块内修改电源按钮屏蔽属性
function c_fructrl:set_PanelPowerButtonEnabled(val)
    log:notice_easy('[System:%s]execute set panel power button val=(%s)', self.system_id, val)
    return set_operation(self.obj, 'PanelPowerButtonEnabled', val)
end

function c_fructrl:set_PowerOnDelayMode(val)
    return set_operation(self.obj, 'Mode', val)
end

function c_fructrl:get_PowerOnDelayMode()
    return get_operation(self.obj, 'Mode')
end

function c_fructrl:set_PowerOnDelaySeconds(val)
    return set_operation(self.obj, 'Seconds', val)
end

function c_fructrl:get_PowerOnDelaySeconds()
    return get_operation(self.obj, 'Seconds')
end

function c_fructrl:set_SysResetDetected(val)
    log:notice_easy('[System:%s]Refresh system reset detected %s', self.system_id, val)
    return set_operation(self.obj, 'SysResetDetected', val)
end

function c_fructrl:set_PowerOnStrategyExceptions(val)
    log:notice_easy('[System:%s]Start to set power on strategy exceptions to %s', self.system_id, val)
    return set_operation(self.obj, 'PowerOnStrategyExceptions', val)
end

function c_fructrl:get_PwrRestoreRecord()
    return get_operation(self.obj, 'PwrRestoreRecord')
end

function c_fructrl:set_PwrRestoreRecord(val)
    return set_operation(self.obj, 'PwrRestoreRecord', val)
end

function c_fructrl:get_BmcResetFlag()
    return get_operation(self.obj, 'BmcResetFlag')
end

function c_fructrl:set_BmcResetFlag(val)
    return set_operation(self.obj, 'BmcResetFlag', val)
end

function c_fructrl:set_last_power_event(val)
    return set_operation(self.obj, 'LastPowerEvent', val)
end

function c_fructrl:get_PwrOnLocked()
    return get_operation(self.obj, 'PwrOnLocked')
end

function c_fructrl:get_PowerOffLocked()
    return get_operation(self.obj, 'PowerOffLocked')
end

function c_fructrl:set_PwrCycleDelaySeconds(val)
    return set_operation(self.obj, 'PwrCycleDelaySeconds', val)
end

function c_fructrl:get_PwrCycleDelaySeconds()
    return get_operation(self.obj, 'PwrCycleDelaySeconds')
end

function c_fructrl:get_PwrStateBeforeACLost()
    return get_operation(self.obj, 'PwrStateBeforeACLost')
end

function c_fructrl:set_PwrStateBeforeACLost(val)
    self:set_PreviousPowerState(val and "ON" or "OFF")
    return set_operation(self.obj, 'PwrStateBeforeACLost', val)
end

function c_fructrl:update_ctrl_fault()
    -- 如果存在上电失败标记或下电失败标记，说明发生电源故障PowerCtrlFault设为1
    if self.pg_signal.poweron_fault or self.pg_signal.poweroff_fault then
        self.obj.PowerCtrlFault = 1
        return
    end
    -- 无失败标记则设置为0，说明无电源故障
    self.obj.PowerCtrlFault = 0
end

function c_fructrl:get_PwrCycleType()
    return get_operation(self.obj, 'PowerCycleType')
end

function c_fructrl:set_PwrCycleType(val)
    return set_operation(self.obj, 'PowerCycleType', val)
end

function c_fructrl:get_PreviousPowerState()
    return get_operation(self.obj, 'PreviousPowerState')
end

function c_fructrl:set_PreviousPowerState(val)
    return set_operation(self.obj, 'PreviousPowerState', val)
end

function c_fructrl:get_ACPISystemPowerState()
    return get_operation(self.obj, 'ACPISystemPowerState')
end

function c_fructrl:set_ACPISystemPowerState(val)
    return set_operation(self.obj, 'ACPISystemPowerState', val)
end

function c_fructrl:get_ACPIDevicePowerState()
    return get_operation(self.obj, 'ACPIDevicePowerState')
end

function c_fructrl:set_ACPIDevicePowerState(val)
    return set_operation(self.obj, 'ACPIDevicePowerState', val)
end

function c_fructrl:set_ACLost(type)
    -- 取值为1,表示powerdrop引起的AClost
    if type == 1 then
        self.board_power_drop = true
        log:notice("[System:%s] Board powerdrop occured, set PwrStateBeforeACLost to true!", self.system_id)
        return enums.RetValue.OK
    end
    log:error("[System:%s] setting the aclost state failed, because of wrong type!", self.system_id)
    error(m_error_custom.ValueOutOfRange('%type'))
end

function c_fructrl:get_FruPowerState(fru_id)
    if fru_id == 0 or not self.fru_pg_signals[fru_id] then
        return self:get_PowerState()
    end
    local pg_state = self.fru_pg_signals[fru_id]:get_PGSignal()
    return pg_state == 1 and 'ON' or 'OFF'
end

function c_fructrl:update_chassis_poweron_strategy(value)
    self.chassis_poweron_strategy = value
    if self.obj then
        self:set_PowerOnStrategy(value)
    end
end

function c_fructrl:set_defaulte_poweron_strategy()
    if self.chassis_poweron_strategy then
        self.set_PowerOnStrategy(self.chassis_poweron_strategy)
    end
end

function c_fructrl:get_ACCycleEnabled(type)
    return get_operation(self.obj, 'ACCycleEnabled')
end

function c_fructrl:set_value_by_name(name, val)
    return set_operation(self.obj, name, val)
end
return c_fructrl